
import traceback
from collections import OrderedDict


from django.utils import timezone
from django.utils.log import RequireDebugTrue

from weiwenhai_Oj import settings

import logging
import os


class PathFileHandler(logging.FileHandler):

    def __init__(self, path, filename, mode='a', encoding=None, delay=False):
        # 确保目录存在
        if not os.path.exists(path):
            os.makedirs(path)  # 使用makedirs递归创建目录

        # 组合文件路径
        self.baseFilename = os.path.join(path, f"{filename}.log")
        self.mode = mode
        self.encoding = encoding
        self.delay = delay

        # 调用父类构造函数
        super().__init__(self.baseFilename, mode, encoding, delay)

    def _open(self):
        """Open the current base file with the (original) mode and encoding."""
        return open(self.baseFilename, self.mode, encoding=self.encoding)


class Loggers(object):
    # 日志级别
    LOG_LEVEL = {
        'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARNING, 'warn': logging.WARN,
        'error': logging.ERROR, 'critical': logging.CRITICAL, 'fatal': logging.FATAL,
    }
    # 日志格式
    FMT_STYLE = {
        "standard": '{"levelname": "%(levelname)s", "asctime": "%(asctime)s", "name": "%(name)s", '
                    '"filename": "%(filename)s", "function": "%(funcName)s", "lineno": %(lineno)d, '
                    '"detail": "%(message)s"}',
        "simple": '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s',
    }
    # 日志根目录
    BASE_LOG_DIR = settings.BASE_LOG_DIR
    # 日志对象字典
    LOG_OBJ_DICT = OrderedDict()
    MAX_LOG_OBJ_NUM = 50

    def __init__(self, dirname, *args, **kwargs):
        """
        :param filename: 日志文件名
        :param args:
        :param kwargs:
            :param level: 日志输出级别，可选：debug
            :param log_dir: 日志目录，默认日志事件触发时的date
            :param fmt_style: 日志格式，默认standard
            :param handler_list: 日志输出方式及等级，默认[("FileHandler", "info"), ("StreamHandler", "debug")]
        """
        self._date = timezone.localtime().strftime("%Y-%m-%d")
        self.dirname = dirname
        self.filename = dirname + "_" + self._date
        self._new_log(*args, **kwargs)

    def _new_log(self, *args, **kwargs):
        self.logger = self.LOG_OBJ_DICT.get(self.filename)
        if self.logger is None:
            self.logger = logging.getLogger(self.filename)
            self._set_level(*args, **kwargs)
            self._set_handlers_and_formatters(*args, **kwargs)
            self.LOG_OBJ_DICT[self.filename] = self.logger
            if len(self.LOG_OBJ_DICT) > self.MAX_LOG_OBJ_NUM:
                self.LOG_OBJ_DICT.popitem(False)

    @staticmethod
    def _formatting_msg(msg):
        """开发（即DEBUG=True）时日志不做换行等字符串处理，方便调试"""
        if settings.DEBUG is False:
            return str(msg).replace("\n", "").replace('"', "'")
        else:
            return str(msg)

    def log_exception(self, msg):
        """
        由于源生logger.exception()会记录多行traceback日志，不便于filebeat收集和json解析，不建议使用！。
        因此，本类提供此方法将多行traceback合并成一行。
        :return:
        """
        traceback_str = self._formatting_msg(traceback.format_exc())
        self.logger.error(msg + " >>> " + traceback_str)

    def _set_level(self, *args, **kwargs):
        level = kwargs.get("level", "debug")
        if not self.LOG_LEVEL.get(level.lower()):
            raise TypeError("Wrong log level type: %s" % level)
        self.log_level = self.LOG_LEVEL.get(level.lower())
        self.logger.setLevel(self.log_level)

    def _set_handlers_and_formatters(self, *args, **kwargs):
        handler_list = kwargs.get("handler_list", [("FileHandler", "info"), ("StreamHandler", "debug")])
        for handler, level in handler_list:
            if not self.LOG_LEVEL.get(level.lower()):
                raise TypeError("Wrong log level type: %s" % level)
            if hasattr(self, "_set" + handler):
                getattr(self, "_set" + handler)(level, *args, **kwargs)
            else:
                raise ValueError("No class method like '_set%s'. Please create this method first!" % handler)

    def _setFileHandler(self, level, *args, **kwargs):
        """日志文件输出"""
        path = os.path.join(self.BASE_LOG_DIR, self.dirname)
        # handler
        file_handler = PathFileHandler(path=path, filename=self.filename, mode='a', encoding="utf-8")
        file_handler.setLevel(self.LOG_LEVEL.get(level.lower()))
        # formatter
        fmt_style = kwargs.get("fmt_style", "standard")
        formatter = logging.Formatter(fmt=self.FMT_STYLE.get(fmt_style), datefmt="%Y/%m/%d %H:%M:%S", style='%')
        file_handler.setFormatter(formatter)
        self.logger.addHandler(file_handler)

    def _setStreamHandler(self, level, *args, **kwargs):
        """控制台输出"""
        # handler
        stream_handler = logging.StreamHandler()
        stream_handler.setLevel(self.LOG_LEVEL.get(level.lower()))
        require_debug_true = RequireDebugTrue()
        stream_handler.addFilter(require_debug_true)
        # formatter
        fmt_style = kwargs.get("fmt_style", "simple")
        formatter = logging.Formatter(fmt=self.FMT_STYLE.get(fmt_style), datefmt="%Y/%m/%d %H:%M:%S", style='%')
        stream_handler.setFormatter(formatter)
        self.logger.addHandler(stream_handler)


def log(dirname):
    """
    返回一个默认配置的日志对象。
    请避免使用.error('', exc_info=True)和.exception()方法，
    如需要记录报错trackback信息，请使用Loggers(dirname).log_exception(self, msg)。
    一般也不需要自行捕获未知错误来记录报错trackback信息，exception_utils.custom_exception_handler会自动处理。
    """
    loggers_obj = Loggers(dirname)
    return loggers_obj.logger


if __name__ == "__main__":
    log("aaa").info("大风起兮云飞扬，安得猛士兮守四方！")
